package day28.homework;

import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
import java.util.concurrent.atomic.AtomicLong;

/**
 * 银行账户类 - 代表一个银行账户，包含账户名、余额和锁机制
 * 用于实现线程安全的银行转账操作
 * 
 * 这个类封装了银行账户的基本属性和操作，通过使用锁机制确保在多线程环境下的
 * 数据一致性。每个账户实例都拥有自己的锁，用于控制对该账户的并发访问。
 */
class Account {
    // 账户名称，用于标识不同的账户，这是一个不可变的字符串属性
    // 使用private修饰符确保封装性，防止外部直接访问和修改
    private String name;           
    // 账户余额，表示账户当前的资金数量，使用double类型存储金额
    // 在实际生产环境中，通常使用BigDecimal来避免浮点数精度问题
    private double balance;        
    // 账户锁，使用ReentrantLock实现线程同步，确保同一时间只有一个线程可以操作该账户
    // ReentrantLock是Java提供的可重入互斥锁，相比synchronized提供了更多的功能
    // 如可中断锁、超时锁、公平锁等特性
    private final Lock lock = new ReentrantLock(); 

    /**
     * 构造方法 - 创建一个新的银行账户
     * @param name 账户名称，用于标识账户的唯一性，不能为空
     * @param balance 初始余额，表示账户创建时的资金数量，必须为非负数
     * 
     * 构造方法使用public访问修饰符，允许外部代码创建Account实例
     * 通过this关键字区分局部变量和实例变量，避免命名冲突
     */
    public Account(String name, double balance) {
        this.name = name;
        this.balance = balance;
    }

    /**
     * 获取账户名称的getter方法
     * @return 账户名称，返回String类型，表示账户的标识符
     * 
     * 遵循JavaBean规范，提供标准的getter方法用于访问私有属性
     * 使用public访问修饰符允许外部代码获取账户名称
     */
    public String getName() {
        return name;
    }

    /**
     * 获取账户余额的getter方法
     * @return 账户当前余额，返回double类型，表示账户的资金数量
     * 
     * 提供对账户余额的只读访问，外部代码可以查询余额但不能直接修改
     * 这样可以确保余额的修改只能通过debit和credit方法进行，保证数据一致性
     */
    public double getBalance() {
        return balance;
    }

    /**
     * 获取账户锁的getter方法
     * @return 该账户的锁对象，用于同步控制，返回Lock接口类型
     * 
     * 提供对账户锁的访问，允许外部代码在需要时手动获取和释放锁
     * 返回Lock接口而不是具体实现类，遵循面向接口编程的原则
     */
    public Lock getLock() {
        return lock;
    }

    /**
     * 从账户中扣除指定金额（借记操作）
     * @param amount 需要扣除的金额，必须为正数
     * 
     * 这是一个非同步方法，调用者需要确保在获取锁的情况下调用此方法
     * 直接修改账户余额，不进行任何验证，调用者需要确保操作的合法性
     * 使用-=操作符进行减法运算并赋值，是balance = balance - amount的简写形式
     */
    public void debit(double amount) {
        balance -= amount;
    }

    /**
     * 向账户中增加指定金额（贷记操作）
     * @param amount 需要增加的金额，必须为正数
     * 
     * 这是一个非同步方法，调用者需要确保在获取锁的情况下调用此方法
     * 直接修改账户余额，不进行任何验证，调用者需要确保操作的合法性
     * 使用+=操作符进行加法运算并赋值，是balance = balance + amount的简写形式
     */
    public void credit(double amount) {
        balance += amount;
    }

    /**
     * 重写toString方法，用于格式化输出账户信息
     * @return 格式化的账户信息字符串，包含账户名和余额
     * 
     * 重写Object类的toString方法，提供有意义的字符串表示
     * 使用String.format方法进行格式化，%.2f表示保留两位小数的浮点数
     * 这样可以确保金额显示的一致性和可读性
     */
    @Override
    public String toString() {
        return String.format("账户名='%s', 余额=%.2f}", name, balance);
    }
}

/**
 * 银行转账类 - 实现线程安全的转账功能
 * 包含转账的核心逻辑，确保在多线程环境下的数据一致性
 * 
 * 该类负责处理账户间的资金转移，通过合理的锁顺序避免死锁问题
 * 并通过双重验证确保转账操作的合法性和安全性
 */
class BankTransfer {
    /**
     * 转账方法 - 实现从一个账户向另一个账户转账的功能
     * 该方法是线程安全的，可以同时处理多个转账请求
     * 
     * 实现了经典的转账逻辑，包括参数验证、余额检查、死锁避免和原子操作
     * 使用两阶段加锁策略确保转账操作的原子性
     * 
     * @param from 转出账户（资金来源账户），不能为null
     * @param to 转入账户（资金目标账户），不能为null
     * @param amount 转账金额，必须为正数
     * 
     * 整个转账过程遵循ACID特性中的原子性，要么全部成功，要么全部失败
     */
    public void transfer(Account from, Account to, double amount) {
        // 第一步：验证转账金额的有效性
        // 确保转账金额大于0，这是业务规则的要求
        // 防止非法的转账操作，如负数转账或零金额转账
        if (amount <= 0) {
            System.out.println("转账金额必须大于0");
            return;
        }

        // 第二步：验证转出账户的余额是否足够
        // 检查转出账户的余额是否大于等于转账金额，防止透支
        // 这是一个重要的业务规则，确保账户不会出现负余额
        if (from.getBalance() < amount) {
            System.out.println("转出账户余额不足，无法完成转账");
            return;
        }

        // 第三步：避免死锁 - 按账户名称的字典序排序锁定账户
        // 通过按名称排序确定加锁顺序，确保所有线程都按相同顺序获取锁，避免死锁
        // 这是解决多锁死锁问题的经典方案：按固定顺序获取锁
        // 使用String的compareTo方法进行字典序比较，返回负数、0或正数
        // 如果from账户名称的字典序小于to账户，则from为第一把锁，否则to为第一把锁
        Account firstLock = from.getName().compareTo(to.getName()) < 0 ? from : to;
        Account secondLock = from.getName().compareTo(to.getName()) < 0 ? to : from;

        // 第四步：按顺序获取锁并执行转账操作
        // 先获取第一把锁，调用lock()方法会阻塞直到获取到锁
        // 这确保了当前线程独占访问该账户，其他线程必须等待
        firstLock.getLock().lock();
        try {
            // 再获取第二把锁，此时已经持有了第一把锁
            // 使用嵌套的try-finally确保锁的正确释放
            secondLock.getLock().lock();
            try {
                // 执行转账的核心操作
                // 从转出账户扣除金额，这是一个简单的减法操作
                from.debit(amount);
                // 向转入账户增加金额，这是一个简单的加法操作
                to.credit(amount);
                
                // 输出转账成功信息，包括转账详情和账户最新余额
                // 使用printf格式化输出，提高可读性
                // %s表示字符串占位符，%.2f表示保留两位小数的浮点数占位符
                // 这里输出了转账双方的账户名、转账金额以及转账后的余额
                System.out.printf("转账成功: %s -> %s, 金额: %.2f, %s余额: %.2f, %s余额: %.2f%n",
                        from.getName(), to.getName(), amount,
                        from.getName(), from.getBalance(),
                        to.getName(), to.getBalance());
            } finally {
                // 确保第二把锁被释放，使用finally块保证即使发生异常也会释放锁
                // unlock()方法必须与lock()方法配对使用，否则会导致锁泄露
                secondLock.getLock().unlock();
            }
        } finally {
            // 确保第一把锁被释放，使用finally块保证即使发生异常也会释放锁
            // finally块的执行顺序是先执行内层finally，再执行外层finally
            firstLock.getLock().unlock();
        }
    }
}

/**
 * 转账任务类 - 实现Runnable接口，用于在多线程环境中执行转账操作
 * 将转账操作封装为可执行的任务
 * 
 * 实现Runnable接口比继承Thread类更灵活，允许任务与线程分离
 * 一个任务可以被多个线程执行，提高了代码的复用性
 */
class TransferTask implements Runnable {
    // 转出账户，使用private修饰符确保封装性
    // final关键字确保引用不会被重新赋值，增强安全性
    private Account from;
    // 转入账户，使用private修饰符确保封装性
    // final关键字确保引用不会被重新赋值，增强安全性
    private Account to;
    // 转账金额，使用private修饰符确保封装性
    // final关键字确保值不会被修改，增强安全性
    private double amount;
    // 银行转账对象，包含实际的转账逻辑
    // final关键字确保引用不会被重新赋值，增强安全性
    private BankTransfer bankTransfer;

    /**
     * 构造方法 - 创建一个转账任务
     * @param from 转出账户，资金的来源账户，不能为null
     * @param to 转入账户，资金的目标账户，不能为null
     * @param amount 转账金额，必须为正数
     * @param bankTransfer 银行转账对象，负责执行实际的转账逻辑，不能为null
     * 
     * 通过构造方法注入依赖，遵循依赖注入的设计原则
     * 所有参数都使用final修饰，确保在构造后不会被修改
     */
    public TransferTask(Account from, Account to, double amount, BankTransfer bankTransfer) {
        this.from = from;
        this.to = to;
        this.amount = amount;
        this.bankTransfer = bankTransfer;
    }

    /**
     * 实现Runnable接口的run方法
     * 当线程启动时会执行此方法中的转账操作
     * 
     * run方法是线程执行的入口点，当线程的start()方法被调用时，JVM会调用此方法
     * 此方法不返回任何值，也不应该抛出受检异常
     * 所有的业务逻辑都委托给bankTransfer对象处理，遵循单一职责原则
     */
    @Override
    public void run() {
        // 调用银行转账对象的transfer方法执行实际的转账操作
        // 将任务的执行委托给专门的业务逻辑处理类
        // 这样做可以提高代码的可维护性和可测试性
        bankTransfer.transfer(from, to, amount);
    }
}

/**
 * 银行账户转账测试类 - 主程序入口，用于测试多线程环境下的转账功能
 * 模拟多个用户同时进行转账操作，验证线程安全性
 * 
 * 这是一个综合测试类，演示了如何在多线程环境中使用Account和BankTransfer类
 * 通过创建多个并发线程来验证转账操作的线程安全性
 */
public class hw11 {
    /**
     * 程序主入口
     * @param args 命令行参数，本程序中未使用
     * @throws InterruptedException 当线程被中断时抛出的异常
     * 
     * main方法是Java程序的入口点，JVM从这里开始执行程序
     * 使用throws声明可能抛出的异常，避免在方法体内处理
     */
    public static void main(String[] args) throws InterruptedException {
        // 创建两个测试账户A和B，初始余额都为1000元
        // 这两个账户将用于模拟转账操作，初始状态具有相同的余额
        Account accountA = new Account("A", 1000);
        Account accountB = new Account("B", 1000);
        
        // 创建银行转账对象，用于执行转账操作
        // 所有的转账逻辑都由这个对象处理，体现了面向对象的设计原则
        BankTransfer bankTransfer = new BankTransfer();
        
        // 创建线程数组，用于管理多个转账线程
        // 数组大小为10，表示将创建10个并发执行的转账线程
        Thread[] threads = new Thread[10];
        
        // 创建5个从账户A转账到账户B的线程，每个转账100元
        // 这些线程将并发执行转账操作，模拟多个用户同时从A账户向B账户转账
        for (int i = 0; i < 5; i++) {
            // 为每个循环迭代创建一个新的Thread对象
            // TransferTask实现了Runnable接口，作为线程的任务
            threads[i] = new Thread(new TransferTask(accountA, accountB, 100, bankTransfer));
        }
        
        // 创建5个从账户B转账到账户A的线程，每个转账50元
        // 这些线程也将并发执行转账操作，模拟多个用户同时从B账户向A账户转账
        for (int i = 5; i < 10; i++) {
            // 为每个循环迭代创建一个新的Thread对象
            // TransferTask实现了Runnable接口，作为线程的任务
            threads[i] = new Thread(new TransferTask(accountB, accountA, 50, bankTransfer));
        }
        
        // 启动所有创建的线程，开始并发执行转账操作
        // start()方法会使线程进入就绪状态，等待CPU调度执行
        // 调用start()方法后，线程会在run()方法中执行任务
        for (int i = 0; i < 10; i++) {
            threads[i].start();
        }
        
        // 等待所有线程执行完毕，使用join方法确保主线程等待所有子线程完成
        // join()方法会阻塞当前线程，直到目标线程执行完毕
        // 这样可以确保在所有转账操作完成后再输出最终结果
        for (int i = 0; i < 10; i++) {
            threads[i].join();
        }
        
        // 输出所有转账操作完成后的最终账户余额
        // 通过比较初始余额和最终余额，可以验证转账操作的正确性
        System.out.println("最终账户余额:");
        // 调用Account对象的toString方法输出格式化的账户信息
        System.out.println(accountA);
        // 调用Account对象的toString方法输出格式化的账户信息
        System.out.println(accountB);
    }
}